package net.apps.core.impl;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

import net.apps.core.Addr;
import net.apps.core.ApplManagerRaw;
import net.apps.core.Channel;
import net.apps.core.ChannelListener;
import net.apps.core.Connection;
import net.apps.core.Message;
import net.apps.core.RawAppl;

public class ApplManagerRawImpl implements ApplManagerRaw {

	private final Thread appl_thread;
	private Class<? extends RawAppl> appl_class;
	private RawConnectionImpl appl_conn;
	private RawAppl the_appl;
	
	public ApplManagerRawImpl() {
		this.appl_thread = Thread.currentThread();
	}

	@Override
	public void instantiateAppl(Class<? extends RawAppl> appl_class) {
		if (appl_thread != Thread.currentThread())
			throw new IllegalStateException("Accessed from another thread");
		if (this.appl_class != null) {
			if (this.appl_class == appl_class)
				return;
			throw new IllegalStateException("Application class already specified");
		}
		this.appl_class = (Class<? extends RawAppl>)appl_class;
	}
	
	public Connection getConnection() {
		return appl_conn;
	}

	private synchronized void requestDataWakeup() {
		if (appl_class == null)
			return;
		this.notifyAll();
	}
	
	@Override
	public synchronized void requestWakeup(RawAppl appl) {
		if (appl_class == null || appl != the_appl)
			return;
		this.notifyAll();
	}
	
	public synchronized void reportIdle(RawAppl appl) throws InterruptedException {
		if (appl != the_appl || appl_class == null)
			return;
		if (Thread.interrupted())
			throw new InterruptedException();
		this.wait();
	}

	@Override
	public void run() {
		if (appl_thread != Thread.currentThread())
			throw new IllegalStateException("Accessed from another thread");
		if (appl_class == null)
			throw new IllegalStateException("Application class not specified");
		try {
			appl_conn = new RawConnectionImpl();
			the_appl = appl_class.getConstructor(ApplManagerRaw.class).newInstance(this);
		} catch (Exception e) {
			e.printStackTrace();
			appl_conn = null;
			return;
		}
		try {
			the_appl.main(this);
		} catch (Exception e) {
			e.printStackTrace();
		}
		the_appl = null;
		appl_conn = null;
	}
	
	private class RawConnectionImpl implements Connection {
		
		private ChannelImpl channel;
		
		@Override
		public Addr getLocalAddr() {
			return null;
		}

		@Override
		public Addr getRemoteAddr() {
			return null;
		}

		@Override
		public Channel getChannel(int id) {
			if (id != 0)
				return null;
			return channel;
		}
		
		class ChannelImpl implements Channel {
			
			private final BlockingQueue<Message> inp = new ArrayBlockingQueue<Message>(16);
			private final BlockingQueue<Message> out = new ArrayBlockingQueue<Message>(16);
			private ChannelListener listener;
			
			@Override
			public Connection getConnection() {
				return RawConnectionImpl.this;
			}

			@Override
			public int getChannelId() {
				return 0;
			}

			@Override
			public ChannelListener getChannelListener() {
				return listener;
			}
			
			@Override
			public void setChannelListener(ChannelListener listener) {
				this.listener = listener;
			}

			@Override
			public boolean push(Message msg, long timeout) {
				boolean added = false;
				try {
					added = out.offer(msg, timeout, TimeUnit.MILLISECONDS);
				} catch (InterruptedException e) {
					e.printStackTrace();
					return false;
				}
				ApplManagerRawImpl.this.requestDataWakeup();
				return added;
			}

			@Override
			public Message take(long timeout) {
				try {
					return inp.poll(timeout, TimeUnit.MILLISECONDS);
				} catch (InterruptedException e) {
					e.printStackTrace();
					return null;
				}
			}

			@Override
			public Message peek() {
				return inp.peek();
			}

			@Override
			public void shutdown() {
			}
		}

	}
}
